Imports System.Threading

Public Module modMain

#Region " Constants "

	Public Const PortNum As Integer = 10000	' random port number
	Public Const BufferSize As Integer = 4096	' bytes

#Region " Our simple text message protocol "

	' Protocol: 
	' these constants describe how our plain text messages are being transferred over TCP
	' in our application.
	' E.g. 
	' "CONNECT|James|JIMPC", 
	' "STATUS|ONLINE", 
	' "CHAT|Hi, there!"
	Public Const msgConnect As String = "CONNECT"	' when initiating connection
	Public Const msgChat As String = "CHAT"	' text message

	Public Const msgStatus As String = "STATUS"	' change status notification (Online / Do Not Disturb)
	Public Const msgStatusOnline As String = "ONLINE"
	Public Const msgStatusDoNotDisturb As String = "DONOTDISTURB"

#End Region

	' Color settings: too lazy to make these runtime-editable :-)
	Public colUser As System.Drawing.Color = System.Drawing.Color.Red	' user's message text
	Public colMe As System.Drawing.Color = System.Drawing.Color.Blue	' my message text

#End Region

	Private WithEvents ChatServer As Chat
	Private WithEvents ChatWindow As MessageWindow

#Region " Main(), initialization & termination "

	Public InstanceMutex As Mutex	' globally visible object

	Public Sub Main()
		If IsAlreadyRunning() Then Return ' check if another instance already running

		Initialize()

		ChatWindow = New MessageWindow()
		ChatWindow.InitializeWindow()
		ChatServer = Chat.Instance		  ' Save a reference to the Chat Singleton for future use.
		ChatServer.Start()

		Application.Run()

		Quit()
		InstanceMutex.Close()
	End Sub

#Region " IsAlreadyRunning "

	'====================================================================
	' This code section determines, if another instance of this process
	' already runs on this machine.
	'====================================================================

	Function IsAlreadyRunning() As Boolean
		' The name of the global mutex should be unique not to mix with other applications.
		Const UniqueString As String = "LANChatAgentByKirillOsenkov"

		' This is the flag to be filled with a boolean result: 
		' True when no instance existed and a new one was just created
		' False when an instance already existed and no action was taken.
		Dim createdNew As Boolean = False

		' try to create a mutex and see, if it already exists
		InstanceMutex = New Mutex(False, UniqueString, createdNew)
		Return Not createdNew
	End Function

	'====================================================================

#End Region

	Public Sub Quit()
		Tray.Visible = False
		If Not ChatServer Is Nothing Then
			ChatServer.Dispose()
			ChatServer = Nothing
		End If
		If Not ChatWindow Is Nothing Then
			ChatWindow.Dispose()
			ChatWindow = Nothing
		End If
	End Sub

#End Region

#Region " Menu & Tray "

#Region " Variable declarations "

	'====================================================================
	Friend WithEvents mnuPopup As System.Windows.Forms.ContextMenu

	Friend WithEvents mnuAddUser As System.Windows.Forms.MenuItem
	Friend WithEvents mnuContactList As System.Windows.Forms.MenuItem
	Friend WithEvents mnuShowChat As System.Windows.Forms.MenuItem
	Friend WithEvents mnuDoNotDisturb As System.Windows.Forms.MenuItem
	Friend WithEvents mnuExit As System.Windows.Forms.MenuItem

	Friend WithEvents mnuSep1 As System.Windows.Forms.MenuItem
	Friend WithEvents mnuSep2 As System.Windows.Forms.MenuItem
	Friend WithEvents mnuSep3 As System.Windows.Forms.MenuItem

	Friend WithEvents Tray As System.Windows.Forms.NotifyIcon
	'====================================================================
#End Region

	Private Sub Initialize()
		PreloadIcons()

		mnuPopup = New System.Windows.Forms.ContextMenu()
		mnuShowChat = New System.Windows.Forms.MenuItem()
		mnuSep1 = New System.Windows.Forms.MenuItem()
		mnuAddUser = New System.Windows.Forms.MenuItem()
		mnuContactList = New System.Windows.Forms.MenuItem()
		mnuSep2 = New System.Windows.Forms.MenuItem()
		mnuDoNotDisturb = New System.Windows.Forms.MenuItem()
		mnuSep3 = New System.Windows.Forms.MenuItem()
		mnuExit = New System.Windows.Forms.MenuItem()
		Tray = New System.Windows.Forms.NotifyIcon()

		mnuPopup.MenuItems.AddRange(New System.Windows.Forms.MenuItem() {mnuShowChat, mnuSep1, mnuAddUser, mnuContactList, mnuSep2, mnuDoNotDisturb, mnuSep3, mnuExit})

		mnuShowChat.DefaultItem = True
		mnuShowChat.Text = "Show chat"
		mnuSep1.Text = "-"
		mnuAddUser.Text = "Add user"
		mnuContactList.Text = "Contact list"
		mnuSep2.Text = "-"
		mnuDoNotDisturb.Text = "Do not disturb"
		mnuSep3.Text = "-"
		mnuExit.Text = "Exit"

		Tray.ContextMenu = mnuPopup
		SetTrayIcon(TrayIcon.LocalOnUsersOff)
		Tray.Text = "User Offline"
		Tray.Visible = True
	End Sub

	' This happens when left-clicking the icon in system tray.
	Private Sub Tray_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Tray.MouseDown
		If e.Button = MouseButtons.Left Then
			ToggleChatWindow()
		End If
	End Sub

	' Auxiliary sub to show or hide the message window. 
	' Thread-safe because calls our protected MessageMinimized property.
	Private Sub ToggleChatWindow()
		MessageMinimized = Not MessageMinimized
	End Sub

	'====================================================================

#Region " Menu events "

	'====================================================================
	' Context menu events
	'====================================================================

	Private Sub mnuContactList_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuContactList.Click
		Dim ContactListDialog As ContactList = New ContactList()
		Dim User As RemoteUser
		For Each User In ChatServer.Users
			ContactListDialog.lstUsers.Items.Add(User.Name & ": " & User.Status.ToString)
		Next
		ContactListDialog.ShowDialog()
	End Sub

	'====================================================================

	Private Sub mnuAddUser_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuAddUser.Click
		Dim AddUserDialog As AddUser = New AddUser()
		If AddUserDialog.ShowDialog <> DialogResult.OK Then Return

		Dim UserName As String = AddUserDialog.txtName.Text
		Dim UserAddress As String = AddUserDialog.txtAddress.Text

		If Not ChatServer.Users(UserName) Is Nothing Then
			MessageBox.Show("User " & UserName & " already exists.")
		Else
			ChatServer.Users.AddUserOutgoingRequest(UserName, UserAddress)
		End If
	End Sub

	'====================================================================

	Private Sub mnuShowChat_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuShowChat.Click
		ToggleChatWindow()
	End Sub

	'====================================================================

	Private Sub mnuExit_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuExit.Click
		Application.Exit()
	End Sub

	'====================================================================

	Private Sub mnuDoNotDisturb_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuDoNotDisturb.Click
		Dim NA As Boolean = Not mnuDoNotDisturb.Checked

		mnuDoNotDisturb.Checked = NA
		If NA <> DoNotDisturb Then
			DoNotDisturb = NA
		End If
	End Sub

	'====================================================================

#End Region

	'====================================================================

#Region " Tray Icons & DND Mode "

	'====================================================================
	' Tray icons
	'====================================================================

	Private Enum TrayIcon
		LocalOnUsersOff
		LocalOnUsersNA
		LocalOnUsersOn
		LocalNAUsersOff
		LocalNAUsersNA
		LocalNAUsersOn
	End Enum

	Private IconTable As Hashtable = New Hashtable()

	Private Sub PreloadIcons()
		Dim TypeInThisAssembly As Type = GetType(Chat)		 ' any type in the assembly with the icons
		IconTable.Add(TrayIcon.LocalOnUsersOn, New System.Drawing.Icon(TypeInThisAssembly, "OnOn.ico"))
		IconTable.Add(TrayIcon.LocalOnUsersNA, New System.Drawing.Icon(TypeInThisAssembly, "OnNa.ico"))
		IconTable.Add(TrayIcon.LocalOnUsersOff, New System.Drawing.Icon(TypeInThisAssembly, "OnOff.ico"))
		IconTable.Add(TrayIcon.LocalNAUsersOn, New System.Drawing.Icon(TypeInThisAssembly, "NaOn.ico"))
		IconTable.Add(TrayIcon.LocalNAUsersNA, New System.Drawing.Icon(TypeInThisAssembly, "NaNa.ico"))
		IconTable.Add(TrayIcon.LocalNAUsersOff, New System.Drawing.Icon(TypeInThisAssembly, "NaOff.ico"))
	End Sub

	Private Sub UpdateTrayIcon()
		If ChatServer.Users.AnyUsersOnline Then
			If ChatServer.Users.AllUsersNotAvailable Then
				If DoNotDisturb Then
					SetTrayIcon(TrayIcon.LocalNAUsersNA)
				Else
					SetTrayIcon(TrayIcon.LocalOnUsersNA)
				End If
			Else
				If DoNotDisturb Then
					SetTrayIcon(TrayIcon.LocalNAUsersOn)
				Else
					SetTrayIcon(TrayIcon.LocalOnUsersOn)
				End If
			End If
		Else
			If DoNotDisturb Then
				SetTrayIcon(TrayIcon.LocalNAUsersOff)
			Else
				SetTrayIcon(TrayIcon.LocalOnUsersOff)
			End If
		End If
	End Sub

	Private Sub SetTrayIcon(ByVal WhichIcon As TrayIcon)
		Tray.Icon = DirectCast(IconTable(WhichIcon), System.Drawing.Icon)
	End Sub

#Region " DoNotDisturb "

	Private mDoNotDisturb As Boolean = False
	Public Property DoNotDisturb() As Boolean
		Get
			Return mDoNotDisturb
		End Get
		Set(ByVal Value As Boolean)
			mDoNotDisturb = Value
			If Value Then
				ChatServer.Users.SendStatus(UserStatus.DoNotDisturb)
			Else
				ChatServer.Users.SendStatus(UserStatus.Online)
			End If
			UpdateTrayIcon()
		End Set
	End Property

#End Region

#End Region

#End Region

#Region " UI interaction "

	'====================================================================

#Region " ChatServer Events "

	'====================================================================
	' Chat events.
	' Use only thread-safe UI interaction.
	'====================================================================

	Private Sub ChatServer_UserConnected(ByVal Sender As Agent.RemoteUser) Handles ChatServer.UserConnected
		Log("Connected to user: " & Sender.Name, System.Drawing.Color.Black)
		UpdateStatusText(Sender.Name & ": Online")

		If DoNotDisturb Then Sender.SendStatus(UserStatus.DoNotDisturb)
	End Sub

	'====================================================================

	Private Sub ChatServer_UserDisconnected(ByVal Sender As Agent.RemoteUser) Handles ChatServer.UserDisconnected
		Log("Disconnected user: " & Sender.Name, System.Drawing.Color.Black)
		UpdateStatusText(Sender.Name & ": Offline")
	End Sub

	'====================================================================

	Private Sub ChatServer_UserReceivedText(ByVal Sender As Agent.RemoteUser, ByVal Text As String) Handles ChatServer.UserReceivedText
		Log(Sender.Name & ": " & Text, colUser)
		ShowChatWindow()
	End Sub

	'====================================================================

	Private Sub ChatServer_UserStatusChanged(ByVal Sender As Agent.RemoteUser) Handles ChatServer.UserStatusChanged
		UpdateStatusText(Sender.Name & " status: " & Sender.Status.ToString)
	End Sub

	'====================================================================

#End Region

	' This event is raised when the user clicks Send button of the MessageWindow form.
	Private Sub ChatWindow_ShouldBroadcastText(ByVal Text As String) Handles ChatWindow.ShouldBroadcastText
		ChatServer.Users.SendText(Text)
	End Sub

#Region " Thread-safe UI interaction "

	'====================================================================
	' Thread safe UI-Interaction

	' You cannot call UI Methods, such as Log, from another thread,
	' you need to use the Invoke mechanismus for this purpose.
	'====================================================================

#Region " Log "

	'====================================================================
	' Log can be called from another thread, e.g. Listener thread (Class LocalPort)
	' or any thread from the thread pool.
	'====================================================================

	Private Delegate Sub LogDelegate(ByVal Text As String, ByVal ForeColor As System.Drawing.Color)

	Private Sub Log(ByVal Text As String, ByVal ForeColor As System.Drawing.Color)
		If ChatWindow Is Nothing Then Exit Sub

		' check if the caller's thread isn't the main UI thread of the application.
		If Not ChatWindow.InvokeRequired Then
			ChatWindow.Log(Text, ForeColor)
		Else
			' indirect intra-thread method invocation
			Dim d As System.Delegate = New LogDelegate(AddressOf Log)
			ChatWindow.BeginInvoke(d, New Object() {Text, ForeColor})
		End If
	End Sub

#End Region

#Region " UpdateStatusText "

	Private Delegate Sub UpdateStatusTextDelegate(ByVal Text As String)

	Private Sub UpdateStatusText(ByVal Text As String)
		If ChatWindow Is Nothing Then Exit Sub

		If Not ChatWindow.InvokeRequired Then
			ChatWindow.Text = Text
			Tray.Text = Text
			UpdateTrayIcon()
		Else
			Dim d As System.Delegate = New UpdateStatusTextDelegate(AddressOf UpdateStatusText)
			ChatWindow.BeginInvoke(d, New Object() {Text})
		End If
	End Sub

#End Region

#Region " Minimize "

	Private Sub ShowChatWindow()
		If Not DoNotDisturb Then MessageMinimized = False
	End Sub

	' This property is thread safe and can be called from another threads.
	Private mMessageMinimized As Boolean = True
	Public Property MessageMinimized() As Boolean
		Get
			Return ChatWindow.MinimizedToTray
		End Get
		Set(ByVal Value As Boolean)
			mMessageMinimized = Value
			SetMinimizedState(Value)
		End Set
	End Property

	Private Delegate Sub SetMinimizedStateDelegate(ByVal Minimized As Boolean)
	Private Sub SetMinimizedState(ByVal Minimized As Boolean)
		If Not ChatWindow.InvokeRequired Then
			ChatWindow.MinimizedToTray = Minimized
		Else
			Dim ChangeWindowStateDelegate As System.Delegate = New SetMinimizedStateDelegate(AddressOf SetMinimizedState)
			' Asynchronous call, this thread doesn't need to wait for the UI thread to finish.
			ChatWindow.BeginInvoke(ChangeWindowStateDelegate, New Object() {Minimized})
		End If
	End Sub

#End Region

	'====================================================================

#End Region

	'====================================================================

#End Region

End Module